Conversation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
👀 AI Code ReviewSomething went wrong: <urlopen error [Errno -2] Name or service not known> Powered by GPT-4o via GitHub Models |
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
👀 AI Code ReviewSomething went wrong: <urlopen error [Errno -2] Name or service not known> Powered by GPT-4o via GitHub Models |
|
There is nothing about long python/perl/nodejs scripts execution and parsing. This one should be processed too. Inline scripts should be pretty formatted |
|
Think about jev (https://typesafe.ai/blog/introducing-system-one-models-and-jev) usage for complicated commands detection |
|
Think about AGENTS.md like instructions extension by plugin, without additional manual user steps |
| - `allow` → untouched, always. | ||
| - `deny` → existing deny flow (wrap + stored deny). Restructuring a forbidden action is meaningless — the format is not the problem. | ||
| - Parse errors → existing fail-closed deny, unaffected. | ||
| - `null` (no plugin opinion) → untouched. Under the documented prerequisite (`"*": "ask"`), every uncovered segment matches the catch-all, so any chain that would reach a human resolves to `ask` — "not allowed" ⇔ `ask` in practice. Without the catch-all the plugin has no opinion and does not intervene. |
There was a problem hiding this comment.
Default permissions settings must be taken into account
There was a problem hiding this comment.
Addressed in 01a5386 — decision 3 now records how default permission settings are accounted for.
Concretely (code-backed, parseConfig in src/config.ts): the entire plugin disables itself when permission.bash is absent (opencode's default state — no user permission config) or resolves to allow (flat "bash": "allow" or object "*": "allow"). So under default permissions neither the chain guard nor restructure runs — no surprising prompts for users who never opted in. restructure can only fire when the user actually configured bash permissions, and under the documented "*": "ask" prerequisite "not allowed ⇔ ask" holds exactly; a config without the catch-all leaves uncovered segments at null (no opinion → untouched).
There was a problem hiding this comment.
the entire plugin disables itself when permission.bash is absent (opencode's default state — no user permission config) or resolves to allow (flat "bash": "allow" or object "*": "allow")
Only one of these may happen by default. We need to know what is opencode's default behaviour, when permssions settings not exist
There was a problem hiding this comment.
Verified — you're right that only one default exists, and it's now recorded precisely (design.md decision 3, 8ee8e5f).
OpenCode's documented defaults are permissive: with no permission settings, bash defaults to "allow" — commands run without any prompt. Only external_directory and doom_loop default to "ask" (Permissions → Defaults).
Consequence for the plugin, now stated exactly in the design: in the default state there is no effective bash rule to resolve against, and parseConfig (src/config.ts) disables the entire plugin (enabled = false when permission.bash is absent, a flat "allow", or "*": "allow" — all resolve to the same inert state). So out of the box: native behavior = allow-all, plugin inert, no prompts introduced. restructure can only ever fire in a user-configured permission setup, and with the documented "*": "ask" prerequisite "not allowed ⇔ ask" holds exactly.
|
|
||
| Flow in `beforeExecute`: parse → resolve chain (existing) → if action is `ask` AND `restructure.enabled` AND limits exceeded → **throw** (replaces wrap+store for that call). Otherwise existing flows verbatim. | ||
|
|
||
| 4. **One-liner targeting: segment limit applies to single-line commands only** |
There was a problem hiding this comment.
Not sure about it. If we have two lines command with a lot of segments, it still should be splitted
There was a problem hiding this comment.
Agreed — replaced the multi-line exemption with a per-line check (01a5386, design decision 4 + spec "Detect command complexity").
The segment limit now applies to every line of every command shape: a single-line command is one line, a multi-line script gets each line checked individually. A 2-line script with many segments per line is rejected, and the message is line-scoped (line 2: 5 chained commands, nesting depth 2) so the model knows exactly which line to fix.
Deadlock-freedom is preserved: the compliant form (one command per line) has every line at 1 segment, so a compliant re-issue always exists.
| - Command contains no newline → both `max_segments` and `max_depth` checks apply. | ||
| - Command contains a newline (multi-line script) → **exempt from the segment limit**; depth check still applies. | ||
|
|
||
| Rationale: the compliant form ("multi-line, one command per line") must never violate the segment limit, otherwise the retry loop can deadlock (a 5-step task re-issued as 5 lines would still exceed `max_segments: 3` and be rejected forever). Multi-line IS the readable form the feature asks for. Deep nesting remains unreadable in any shape, so `max_depth` applies to both forms. Known edge, accepted for v1: a multi-line script with long `&&`-chains inside individual lines passes the segment check (ask still gates uncovered segments; a per-line segment check is possible future refinement). |
There was a problem hiding this comment.
Known edge, accepted for v1: a multi-line script with long
&&-chains inside individual lines passes the segment check (ask still gates uncovered segments; a per-line segment check is possible future refinement).
If whole command will be constructed on && and || operands, it wouldn't be splitted? I'm afraid that it is frequent case
There was a problem hiding this comment.
You're right, and it is the frequent case — fixed by the same change as the thread above (01a5386): the "accepted v1 edge" is gone. The segment limit now applies per line to every command shape, so a "multi-line" command built on &&/|| chains is checked line-by-line; any line over max_segments on an ask-resolving chain is rejected with guidance to split it further (design decision 4, risks updated, spec scenario "Multi-line script with long per-line chains — rejected" + task 4.4 added).
The always-compliant form remains one command per line (1 segment per line), so the retry loop cannot deadlock while the &&-wrapped blobs can no longer slip through the multi-line shape.
…faults + review notes - Decision 4: segment limit applies per line to every command shape (multi-line is not an exemption); rejection message names the offending line - Decision 9 + spec/tasks: interpreter inline scripts (python -c, node -e, perl -e, ...) complexity-checked via statement count; pretty formatting (one statement per line) as the compliant form - Decision 3: record how default permission settings are accounted for (plugin self-disables when bash permission config is absent or allow-all) - Future directions: plugin-injected instructions, TypeSafe Jev classifier Addresses review threads and owner comments on PR #17. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
👀 AI Code ReviewSomething went wrong: <urlopen error [Errno -2] Name or service not known> Powered by GPT-4o via GitHub Models |
|
Added to scope in 01a5386 (design decision 9, new spec requirement "Complexity-check interpreter inline scripts", tasks 2.5/3.1–3.4/4.4). What's covered: Pretty formatting as the compliant form: over-threshold inline scripts are rejected with interpreter-specific guidance — re-issue with one statement per line inside the quoted script ( Deliberately a deterministic heuristic (no per-language parsers in v1); the miscount risk (semicolons inside strings) is documented under Risks — it errs toward rejection of unreadable blobs, and per-language AST parsing is a possible refinement. |
|
Researched the post — Jev is a good conceptual fit for exactly the fuzzy part of this problem. Where it fits: our detection is AST-threshold based ( Why not in v1 (recorded under design.md → Future Directions in 01a5386):
Proposed follow-up: an optional, off-by-default detector backend ( |
👀 AI Code ReviewSomething went wrong: <urlopen error [Errno -2] Name or service not known> Powered by GPT-4o via GitHub Models |
…mental.chat.system.transform
👀 AI Code ReviewSomething went wrong: <urlopen error [Errno -2] Name or service not known> Powered by GPT-4o via GitHub Models |
|
Re: plugin-managed AGENTS.md instructions without manual user steps — yes, this is possible today. OpenCode's plugin API has an experimental hook A few related facts:
Because the hook is experimental, I kept the AGENTS.md snippet as the v1 soft layer and recorded auto-injection as a future direction in |
…llow" Permissions docs (Permissions → Defaults): with no permission settings, most permissions default to "allow"; only external_directory and doom_loop default to "ask". So the out-of-box state is allow-all bash; parseConfig self-disables the plugin there (absent bash config, flat "allow", or "*": "allow" all resolve to the same inert state). restructure can only fire in a user-configured permission setup. Clarifies review thread r4130311247 on PR #17.
Summary
OpenSpec change proposal for
chain-restructuring— deterministic steering: the plugin rejects not-allowed complex one-liners with an actionable error, so the agent re-issues them as separate commands or a multi-line script (one command per line), each checked individually. Allowed chains stay allowed — restructuring targets exactly the commands a human must review. Proposal only; implementation follows after review.Why
Not-allowed multi-step commands surface to the human as an unreadable one-liner in the permission dialog, and the agent has no incentive to write readable commands. AGENTS.md instructions are soft (probabilistic, no verification loop).
Verified mechanism (from opencode API research)
permission.askoutput carries only{ status }— no reason field; per issue [FEATURE]: Wire the permission.ask plugin hook anomalyco/opencode#19469 the hook isn't even triggered by the engine in current source. Unusable for steering.tool.execute.before+throwworks: thrown errors become tool results withresultType: "error"and the error text reaches the model (official docs pattern). The model retries in compliant form — a closed deterministic loop.Proposed config — separate plugin file
opencode-bash-guard.jsoncin the opencode config dirs (global~/.config/opencode/, project.opencode/), JSONC with comments, deep-merged project over global. Permission actions stay inopencode.json; plugin behavior tuning lives here.{ // Reject complex one-liners and ask the agent to restructure them "restructure": { "enabled": false, // default false — zero behavior change "max_segments": 3, // max commands in a single-line chain "max_depth": 2 // max $()/backtick/meta-command nesting } }Key design decisions
"*": "ask"prerequisite, "not allowed" ⇔askin practiceparseChain) + max substitution nesting depth (new — catchesecho $(echo $(...))obfuscation)Artifacts
proposal.md— what & whydesign.md— 8 decisions + risks (review decision recorded: restructure applies only to not-allowed commands)specs/chain-restructuring/spec.md— 6 requirements, 26 scenariostasks.md— 6 sections, 29 tasks (incl. new config-loader module +jsonc-parserdep)Out of scope (flagged)
permission.askmay never fire in current opencode (anomalyco/opencode#19469) — the deny path of this plugin may not hard-block. Needs separate verification + possible fix change.Next steps
/opsx-applyto implementUltraworked with Sisyphus